export interface ChatItem {
  /** 相对起始时间（秒） */
  time: number;
  /** 聊天消息文本 */
  message: string;
  /** 可选：用户名或来源 */
  author?: string;
  /** 可选：消息颜色（#RRGGBB） */
  color?: string;
}

export interface AssOptions {
  /** 画面宽度（像素），默认 1080 */
  width: number;
  /** 画面高度（像素），默认 1920 */
  height: number;
  /** 右下角区域最多显示的行数，默认 10 */
  maxLines: number;
  /** 字体名称，默认 Arial（可换为支持 Emoji 的字体） */
  fontName: string;
  /** 字号，默认 36 */
  fontSize: number;
  /** 右边距（像素），默认 60 */
  marginRight: number;
  /** 下边距（像素），默认 60 */
  marginBottom: number;
  /** 左边距（像素），默认 60 */
  marginLeft: number;
  /** 行高（像素），默认 50 */
  lineHeight: number;
  /** 新消息滑入动画时长（毫秒），默认 300ms */
  slideInMs: number;
  /** 末条消息在最后一条到达后额外停留的时长（秒），默认 6 */
  tailHoldSeconds: number;
  /** 样式主颜色（#RRGGBB），默认 #FFFFFF */
  primaryColor: string;
  /** 描边颜色（#RRGGBB），默认 #000000 */
  outlineColor: string;
  /** 可选：底色（#RRGGBB，含半透明），默认 #000000 透明度 0x32 */
  backColorAlphaHex?: string; // 透明度（00-FF），默认 32（约 50%）
  backColor?: string; // 背景色（#RRGGBB），默认 #000000
  /** 描边粗细，默认 2.4 */
  outline: number;
  /** 阴影，默认 0 */
  shadow: number;
  /** 是否启用动画（\move 滑入/上移），默认 true */
  animate: boolean;
  /** 对齐位置：left 或 right，默认 right（底部右侧） */
  align: 'left' | 'right';
}

const defaultOptions: AssOptions = {
  width: 1080,
  height: 1920,
  maxLines: 10,
  fontName: 'Arial',
  fontSize: 36,
  marginRight: 60,
  marginBottom: 60,
  marginLeft: 60,
  lineHeight: 50,
  slideInMs: 300,
  tailHoldSeconds: 6,
  primaryColor: '#FFFFFF',
  outlineColor: '#000000',
  backColorAlphaHex: '32', // 半透明背景
  backColor: '#000000',
  outline: 2.4,
  shadow: 0,
  animate: false,
  align: 'left',
};

function toAssTimestamp(seconds: number): string {
  const s = Math.max(0, seconds);
  const h = Math.floor(s / 3600);
  const m = Math.floor((s % 3600) / 60);
  const sec = Math.floor(s % 60);
  const cs = Math.floor((s - Math.floor(s)) * 100);
  const pad2 = (n: number) => (n < 10 ? `0${n}` : `${n}`);
  const pad2cs = (n: number) => (n < 10 ? `0${n}` : `${n}`);
  return `${h}:${pad2(m)}:${pad2(sec)}.${pad2cs(cs)}`;
}

function hexToAssColor(hex: string, alphaHex = '00'): string {
  // 输入 #RRGGBB，输出 &HAA BB GG RR&（ASS BGR + Alpha）
  const m = hex.trim().match(/^#?([0-9a-fA-F]{6})$/);
  const v = m ? m[1] : 'FFFFFF';
  const rr = v.slice(0, 2);
  const gg = v.slice(2, 4);
  const bb = v.slice(4, 6);
  return `&H${alphaHex}${bb}${gg}${rr}`;
}

function escapeAssText(text: string): string {
  const t = text.replace(/\r\n|\r|\n/g, '\\N').replace(/[{}]/g, '');
  return t;
}

interface ActiveRecord {
  item: Required<Pick<ChatItem, 'message'>> & ChatItem;
  /** 当前段的起始时间（用于切分段） */
  segStart: number;
  /** 当前锚点 Y（以样式对齐为底右对齐） */
  y: number;
  /** 若设置，则本段开始时进行从 moveFromY 到 y 的滑入/上移动画 */
  moveFromY?: number;
}

export interface GenerateAssResult {
  ass: string;
}

export function generateAss(
  input: ChatItem[],
  opts: Partial<AssOptions> = {}
): GenerateAssResult {
  const o: AssOptions = { ...defaultOptions, ...opts };
  const width = o.width;
  const height = o.height;
  const maxLines = Math.max(1, o.maxLines);
  const marginRight = o.marginRight;
  const marginBottom = o.marginBottom;
  const lineHeight = o.lineHeight;
  const slideInMs = o.slideInMs;

  const stylePrimary = hexToAssColor(o.primaryColor, '00');
  const styleOutline = hexToAssColor(o.outlineColor, '00');
  const styleBack = hexToAssColor(o.backColor || '#000000', o.backColorAlphaHex || '32');
  const alignNum = o.align === 'left' ? 1 : 3;

  const header = [
    '[Script Info]',
    'Title: ChatScroll',
    'ScriptType: v4.00+',
    'WrapStyle: 2',
    `PlayResX: ${width}`,
    `PlayResY: ${height}`,
    'ScaledBorderAndShadow: yes',
    '',
    '[V4+ Styles]',
    'Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, ' +
      'Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding',
    `Style: Chat, ${o.fontName}, ${o.fontSize}, ${stylePrimary}, &H000000FF, ${styleOutline}, ${styleBack}, 0, 0, 0, 0, 100, 100, 0, 0, 1, ${o.outline}, ${o.shadow}, ${alignNum}, ${o.marginLeft}, ${marginRight}, ${marginBottom}, 1`,
    '',
    '[Events]',
    'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text',
  ].join('\n');

  const items = [...input]
    .filter((x) => typeof x.time === 'number' && x.message && x.message.length > 0)
    .sort((a, b) => a.time - b.time);

  const dialogs: string[] = [];
  const active: ActiveRecord[] = [];

  const bottomY = (idxFromBottom: number) => height - marginBottom - idxFromBottom * lineHeight;
  const anchorX = o.align === 'left' ? o.marginLeft : (width - marginRight);

  const flushSegment = (rec: ActiveRecord, until: number, useMove = false, moveFromY?: number, moveToY?: number) => {
    if (until <= rec.segStart) return;
    const start = toAssTimestamp(rec.segStart);
    const end = toAssTimestamp(until);
    const name = rec.item.author ? rec.item.author : '';
    const msgColor = rec.item.color ? hexToAssColor(rec.item.color, '00') : undefined;
    const baseTag = msgColor ? `{\\an${alignNum}\\c${msgColor}}` : `{\\an${alignNum}}`;
    // 将用户名加到显示文本中
    const displayText = rec.item.author ? `${rec.item.author}: ${rec.item.message}` : rec.item.message;
    const text = escapeAssText(displayText);
    // 关键修复：将运动标签（\\pos/\\move）包裹在花括号中，避免被播放器当作普通文本显示
    let motionTag = `{\\pos(${anchorX},${rec.y})}`;
    if (useMove && typeof moveFromY === 'number' && typeof moveToY === 'number') {
      motionTag = `{\\move(${anchorX},${moveFromY},${anchorX},${moveToY},0,${slideInMs})}`;
    }
    dialogs.push(
      `Dialogue: 0,${start},${end},Chat,${name},0,0,0,,${baseTag}${motionTag}${text}`
    );
  };

  if (items.length === 0) {
    const ass = `${header}\n`;
    return { ass };
  }

  const lastArrival = items[items.length - 1].time;
  const tailEnd = lastArrival + o.tailHoldSeconds;

  for (let idx = 0; idx < items.length; idx++) {
    const item = items[idx];
    const t = item.time;

    // 如果已满，挤掉最旧的（队头），并在 t 结束它们的当前段
    while (active.length >= maxLines) {
      const removed = active.shift()!;
      flushSegment(removed, t);
    }

    // 现有条目位置上移一行（不立即 flush，本段起点设为 t）
    const m = active.length;
    const newActiveCount = Math.min(maxLines, m + 1);
    for (let i = 0; i < m; i++) {
      const rec = active[i];
      const prevIdxFromBottom = m - 1 - i;
      const newIdxFromBottom = prevIdxFromBottom + 1; // 上移一行
      const prevY = bottomY(prevIdxFromBottom);
      const nextY = bottomY(newIdxFromBottom);
      rec.segStart = t;
      rec.moveFromY = prevY;
      rec.y = nextY;
    }

    // 新消息加入队列尾部（最新在底部），从略低位置滑入
    const newRec: ActiveRecord = {
      item,
      segStart: t,
      y: bottomY(0),
      moveFromY: bottomY(0) + Math.round(lineHeight * 0.6),
    };
    active.push(newRec);

    // 计算下一个事件时间：寻找下一条“时间大于当前 t”的消息；若不存在则使用 tailEnd。
    // 这样可以避免相同时间戳导致的 0 时长段。
    const minDurationSec = Math.max(slideInMs / 1000, 0.3);
    let nextT = tailEnd;
    for (let j = idx + 1; j < items.length; j++) {
      if (items[j].time > t) {
        nextT = items[j].time;
        break;
      }
    }
    // 确保至少有一个最短显示时长（用于展示动画），避免 Start==End 的零时长
    if (nextT <= t) nextT = t + minDurationSec;

    // 将当前所有活动条目在 [segStart, nextT] 期间输出一段
    for (const rec of active) {
    const useMove = o.animate && slideInMs > 0 && typeof rec.moveFromY === 'number';
    flushSegment(rec, nextT, useMove, rec.moveFromY, rec.y);
    rec.segStart = nextT;
    rec.moveFromY = undefined;
  }
  }

  const ass = `${header}\n${dialogs.join('\n')}\n`;
  return { ass };
}

/**
 * 将 ASS 字幕写入文件（可选）
 */
export async function writeAssToFile(filePath: string, assContent: string): Promise<void> {
  const fs = await import('fs');
  await fs.promises.writeFile(filePath, assContent, { encoding: 'utf8' });
}

/** 使用示例：
 * const list: ChatItem[] = [
 *   { time: 0.5, message: '欢迎来到直播间' },
 *   { time: 2.0, message: '今天状态不错', color: '#FFAA00' },
 *   { time: 3.8, message: '主播加油！' },
 * ];
 * const { ass } = generateAss(list, { width: 1080, height: 1920, fontSize: 36, tailHoldSeconds: 6 });
 * await writeAssToFile('chat.ass', ass);
 */